package com.buddy.learn.javasource._io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * NIO是非阻塞IO，其核心组件就是多路复用器Selector和 Channel，
 *      所有的channel都要在Selector上去注册，来实现非阻塞的过程；
 * Selector提供选择已经就绪的任务的能力：
 *      Selector会不断轮询注册在其上的Channel，如果某个Channel上面发生读或者写事件，
 *      这个Channel就处于就绪状态，会被Selector轮询出来，
 *      然后通过SelectionKey可以获取就绪Channel的集合，进行后续的I/O操作。
 * 一个Selector可以同时轮询多个Channel，
 * 因为JDK使用了epoll()代替传统的select实现，所以没有最大连接句柄1024/2048的限制。
 * 所以，只需要一个线程负责Selector的轮询，就可以接入成千上万的客户端。
 * @author Buddy
 * @date 2021/5/16
 */
public class NIOServer_bak {


    public static void main(String[] args) {
        try {
            // 创建一个选择器 selector
            Selector selector = Selector.open();//打开多路复用器

            // 创建一个在本地端口进行监听的服务Socket通道，并设置为非阻塞方式
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//打开socketchannel
            // 必须配置为非阻塞才能往select上注册，否则会报错，select模式本身就是非阻塞模式
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(9000));
            // 把ServerSocketChannel注册到selector上，并且 selector对客户端accept链接操作感兴趣
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                System.out.println("等待事件发生...");
                // 轮询监听 channel 里的key，select 是阻塞的，
                int select = selector.select();
                System.out.println("有事件发生了...");
                // 有客户端请求，被轮询监听到
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    // 删除本次已经处理的key，防止下次 select 重复处理
                    iterator.remove();
                    handle(selectionKey);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static void handle(SelectionKey selectionKey) throws IOException {
        // 判断是否为接收操作
        if (selectionKey.isAcceptable()) {
            System.out.println("有客户端链接事件发生了..");
            //获取key值所对应的channel
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            // NIO 非阻塞体现：此处 accept 方法是阻塞的，但这里因为是发生了连接事件，所以这个方法会马上执行，不会阻塞
            // 处理完这个请求不会继续等待客户端的数据发送
            //设置为接收非阻塞通道
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            // 通过 Selector 监听Channel 时对读事件感兴趣
            //并把这个通道注册为OP_READ
            socketChannel.register(selectionKey.selector(),SelectionKey.OP_READ);
        }
        //判断所传的SelectionKey值是否为OP_READ,通过上面的注册后，经过轮询后就会是此操作
        else if (selectionKey.isReadable()){
            System.out.println("有客户端数据可读事件发生了...");
            SocketChannel sc = (SocketChannel) selectionKey.channel();//获取key对应的channel
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // NIO 非阻塞体现：首先 read 方法不会阻塞，其次这种事件响应模型，当调用到 read 方法时，肯定是发生了客户端发送数据的事件
            int len = sc.read(buffer);
            if (len != -1){
                System.out.println("读到客户端发送的数据："+new String(buffer.array(),0,len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("Hello NIO Client".getBytes());
            sc.write(bufferToWrite);
            selectionKey.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
        } else if (selectionKey.isWritable()){
            SocketChannel sc = (SocketChannel) selectionKey.channel();
            System.out.println("write 事件");
            // NIO 事件触发是水平处罚
            // 使用 Java 的NIO编程的时候，在没有数据可以往外写的时候要取消写事件
            // 在有数据往外写的时候注册写事件
            selectionKey.interestOps(SelectionKey.OP_READ);
//            sc.close();
        }
    }
}
